Vamos tentar entender o impacto da complexidade do modelo na magnitude dos coeficientes. Como exemplo, vamos simular uma curva de seno (entre 60 ° e 300 °) e adicionar algum ruído aleatório usando o seguinte código:
In [1]:
# Import
import numpy as np
import pandas as pd
import random
import matplotlib.pyplot as plt
%matplotlib inline
from matplotlib.pylab import rcParams
rcParams['figure.figsize'] = 12, 10
# Definindo array de input com valores randômicos
x = np.array([i*np.pi/180 for i in range(60,300,4)])
np.random.seed(10)
y = np.sin(x) + np.random.normal(0,0.15,len(x))
data = pd.DataFrame(np.column_stack([x,y]),columns=['x','y'])
plt.plot(data['x'],data['y'],'.')
Out[1]:
Isso se assemelha a uma curva de seno, mas não exatamente por causa do ruído. Usaremos isso como um exemplo para testar diferentes cenários. Vamos tentar estimar a função seno usando a regressão polinomial com potências de x na forma 1 a 15. Isso permite adicionar uma coluna para cada potência até 15 em nosso dataframe. Isso pode ser feito usando o seguinte código:
In [2]:
for i in range(2,16):
colname = 'x_%d'%i
data[colname] = data['x']**i
print (data.head())
Agora que temos todos as 15 potências, vamos construir 15 modelos de regressão linear diferentes com cada modelo contendo variáveis com potências de x. Por exemplo, o conjunto de características do modelo 8 será - {x, x_2, x_3, ..., x_8}.
Primeiro, definiremos uma função genérica que absorverá a potência máxima requerida de x como entrada e retornará uma lista contendo: [modelo RSS, interceptação, coef_x, coef_x2, ... até a potência introduzida]. Aqui, RSS refere-se a "Soma Residual dos Quadrados" ou Residual Sum of Square, que é a soma do quadrado de erros entre os valores previstos e reais no conjunto de dados de treinamento. O código Python que define a função é:
In [3]:
# Import
from sklearn.linear_model import LinearRegression
# Modelo
def linear_regression(data, power, models_to_plot):
predictors=['x']
if power>=2:
predictors.extend(['x_%d'%i for i in range(2,power+1)])
# Fit do modelo
linreg = LinearRegression(normalize=True)
linreg.fit(data[predictors],data['y'])
y_pred = linreg.predict(data[predictors])
# Plot
if power in models_to_plot:
plt.subplot(models_to_plot[power])
plt.tight_layout()
plt.plot(data['x'],y_pred)
plt.plot(data['x'],data['y'],'.')
plt.title('Plot Para a Potência: %d'%power)
# RSS
rss = sum((y_pred-data['y'])**2)
ret = [rss]
ret.extend([linreg.intercept_])
ret.extend(linreg.coef_)
return ret
Observe que esta função não irá plotar o ajuste do modelo para todas as potências, mas irá retornar o RSS e os coeficientes para todos os modelos.
Agora, podemos executar todos os 15 modelos e comparar os resultados. Para facilitar a análise, vamos armazenar todos os resultados em um dataframe do Pandas e plotar 6 modelos para ter uma ideia da tendência. Considere o seguinte código:
Esperamos que os modelos com crescente complexidade melhorem os dados e resultem em valores RSS mais baixos. Isso pode ser verificado observando os gráficos gerados para 6 modelos:
In [4]:
# Inicializa o dataframe para armazenar os resultados
col = ['rss','intercept'] + ['coef_x_%d'%i for i in range(1,16)]
ind = ['model_pow_%d'%i for i in range(1,16)]
coef_matrix_simple = pd.DataFrame(index=ind, columns=col)
# Defina as potências para as quais é necessário um plot
models_to_plot = {1:231,3:232,6:233,9:234,12:235,15:236}
# Iteração através de todas as potências e assimilação dos resultados
for i in range(1,16):
coef_matrix_simple.iloc[i-1,0:i+2] = linear_regression(data, power = i, models_to_plot = models_to_plot)
À medida que a complexidade do modelo aumenta, os modelos tendem a ajustar desvios ainda menores no conjunto de dados de treinamento. Embora isso leve ao overfitting, deixamos essa questão de lado por algum tempo e chegamos ao nosso principal objetivo, ou seja, o impacto na magnitude dos coeficientes. Isso pode ser analisado observando o dataframe criado acima.
In [5]:
# Define o formato de exibição como científico para facilitar a análise
pd.options.display.float_format = '{:,.2g}'.format
coef_matrix_simple
Out[5]:
O tamanho dos coeficientes aumenta exponencialmente com aumento na complexidade do modelo. Espero que isso dê alguma intuição sobre porque colocar uma restrição na magnitude dos coeficientes pode ser uma boa ideia para reduzir a complexidade do modelo.
Vamos tentar entender isso ainda melhor.
O que um coeficiente grande significa? Isso significa que estamos colocando muita ênfase nesse recurso, ou seja, a característica particular é um bom preditor para o resultado. Quando se torna muito grande, o algoritmo inicia a modelagem de relações intrínsecas para estimar a saída e acaba sendo tendo overfitting para os dados de treinamento específicos. Isso é o overfitting e a regularização pode ser a solução para resolver ou pelo menos atenuar o problema.
LASSO representa o Operador de Menor Absoluto de Contração e Seleção. Eu sei que a definição não é muito intuitiva, mas existem duas palavras-chave: "absoluto" e "seleção".
Vamos considerar o primeiro e se preocupar com o último mais tarde.
A regressão Lasso executa a regularização L1, ou seja, acrescenta um fator de soma do valor absoluto dos coeficientes no objetivo de otimização. Assim, a regressão LASSO otimiza o seguinte:
Objetivo = RSS + α * (soma do valor absoluto dos coeficientes)
Aqui, α (alfa) fornece um trade-off entre o balanceamento do RSS e a magnitude dos coeficientes.
Vamos executar a regressão LASSO no mesmo problema descrito acima. Primeiro, definiremos uma função genérica:
In [6]:
from sklearn.linear_model import Lasso
def lasso_regression(data, predictors, alpha, models_to_plot={}):
lassoreg = Lasso(alpha = alpha, normalize = True, max_iter = 1e5)
lassoreg.fit(data[predictors],data['y'])
y_pred = lassoreg.predict(data[predictors])
if alpha in models_to_plot:
plt.subplot(models_to_plot[alpha])
plt.tight_layout()
plt.plot(data['x'],y_pred)
plt.plot(data['x'],data['y'],'.')
plt.title('Plot for alpha: %.3g'%alpha)
rss = sum((y_pred-data['y'])**2)
ret = [rss]
ret.extend([lassoreg.intercept_])
ret.extend(lassoreg.coef_)
return ret
Observe os parâmetros adicionais definidos na função Lasso - 'max_iter'. Este é o número máximo de iterações para as quais queremos que o modelo seja executado caso não ocorra a convergência antes.
Vamos verificar a saída para 10 valores diferentes de alfa usando o seguinte código:
In [7]:
# InicializA os preditores de todas as 15 potências de x
predictors=['x']
predictors.extend(['x_%d'%i for i in range(2,16)])
# Define os valores alfa para testar
alpha_lasso = [1e-15, 1e-10, 1e-8, 1e-5,1e-4, 1e-3,1e-2, 1, 5, 10]
# Inicializa o dataframe para armazenar coeficientes
col = ['rss','intercept'] + ['coef_x_%d'%i for i in range(1,16)]
ind = ['alpha_%.2g'%alpha_lasso[i] for i in range(0,10)]
coef_matrix_lasso = pd.DataFrame(index=ind, columns=col)
# Define os modelos para o Plot
models_to_plot = {1e-10:231, 1e-5:232,1e-4:233, 1e-3:234, 1e-2:235, 1:236}
# Iteração sobre os 10 valores alfa:
for i in range(10):
coef_matrix_lasso.iloc[i,] = lasso_regression(data, predictors, alpha_lasso[i], models_to_plot)
Isso novamente nos diz que a complexidade do modelo diminui com o aumento dos valores de alfa. Mas observe a linha reta em alfa = 1. Parece um pouco estranho não?. Vamos explorar isso ainda mais, analisando os coeficientes.
Além da inferência esperada de RSS para alphas mais elevados, podemos ver o seguinte:
Para os mesmos valores de alfa, os coeficientes de regressão LASSO são muito menores em comparação com o da regressão Ridge (compare a linha 1 das 2 tabelas).
Para o mesmo alfa, o LASSO tem maior RSS (ajuste mais desfavorável) em comparação com a regressão Ridge.
Muitos dos coeficientes são zero mesmo para valores muito pequenos de alfa.
As inferências # 1,2 podem não generalizar sempre, mas serão válidas para muitos casos. Vamos verificar o número de coeficientes que são zero em cada modelo usando o seguinte código:
In [8]:
coef_matrix_lasso.apply(lambda x: sum(x.values==0),axis=1)
Out[8]:
Podemos observar que mesmo para um pequeno valor de alfa, um número significativo de coeficientes é zero. Isso também explica o ajuste de linha horizontal para alfa = 1 nas parcelas de LASSO. Esse fenômeno da maioria dos coeficientes sendo zero é chamado de "sparsity". Embora o LASSO realize a seleção de características, esse nível de sparsity é alcançado somente em casos especiais.
Isso tem algumas implicações realmente interessantes nos casos de uso da regressão LASSO em comparação com a regressão Ridge.
Como mencionado anteriormente, a regressão Ridge executa a "regularização L2", ou seja, adiciona um fator de soma de quadrados de coeficientes no objetivo de otimização. Assim, a regressão Ridge optimiza o seguinte:
Objetivo = RSS + α * (soma do quadrado dos coeficientes)
Aqui, α (alfa) é o parâmetro que equilibra a quantidade de ênfase dada à minimização de RSS, minimizando a soma do quadrado de coeficientes. Α pode ter vários valores:
Espero que isso dê algum sentido sobre o impacto da magnitude dos coeficientes. Uma coisa é certa: qualquer valor não-zero daria valores inferiores aos da regressão linear simples. Por quanto? Vamos ver a regressão Ridge em ação no mesmo problema que o anterior.
Primeiro, vamos definir uma função genérica para regressão Ridge semelhante à definida para regressão linear simples. O código Python é:
In [9]:
from sklearn.linear_model import Ridge
def ridge_regression(data, predictors, alpha, models_to_plot={}):
# Fit do modelo usando Regressão Rdige
ridgereg = Ridge(alpha = alpha, normalize = True)
ridgereg.fit(data[predictors],data['y'])
y_pred = ridgereg.predict(data[predictors])
# Verificamos se um gráfico deve ser feito para o alfa inserido
if alpha in models_to_plot:
plt.subplot(models_to_plot[alpha])
plt.tight_layout()
plt.plot(data['x'],y_pred)
plt.plot(data['x'],data['y'],'.')
plt.title('Plot for alpha: %.3g'%alpha)
# Retorna o resultado em formato pré-definido
rss = sum((y_pred-data['y'])**2)
ret = [rss]
ret.extend([ridgereg.intercept_])
ret.extend(ridgereg.coef_)
return ret
Observe a função 'Ridge' usada aqui. É preciso 'alfa' como parâmetro na inicialização. Além disso, tenha em mente que a normalização dos inputs geralmente é uma boa ideia em todos os tipos de regressão e também deve ser usada no caso de regressão Ridge.
Agora, analisemos o resultado da regressão de Ridge para 10 valores diferentes de α variando de 1e-15 a 20. Esses valores foram escolhidos para que possamos analisar facilmente a tendência com alteração nos valores de α. Estes, no entanto, diferem de caso para caso.
Observe que cada um desses 10 modelos conterá todas as 15 variáveis e somente o valor de alfa seria diferente. Isso é diferente do caso de regressão linear simples em que cada modelo tinha um subconjunto de recursos.
Código Python:
In [10]:
predictors=['x']
predictors.extend(['x_%d'%i for i in range(2,16)])
alpha_ridge = [1e-15, 1e-10, 1e-8, 1e-4, 1e-3,1e-2, 1, 5, 10, 20]
col = ['rss','intercept'] + ['coef_x_%d'%i for i in range(1,16)]
ind = ['alpha_%.2g'%alpha_ridge[i] for i in range(0,10)]
coef_matrix_ridge = pd.DataFrame(index=ind, columns=col)
models_to_plot = {1e-15:231, 1e-10:232, 1e-4:233, 1e-3:234, 1e-2:235, 5:236}
for i in range(10):
coef_matrix_ridge.iloc[i,] = ridge_regression(data, predictors, alpha_ridge[i], models_to_plot)
Aqui podemos observar claramente que, à medida que o valor de alfa aumenta, a complexidade do modelo reduz. Embora os valores mais elevados de alfa reduzam o overfitting, valores significativamente altos também podem causar adequação (por exemplo, alfa = 5). Assim, alfa deve ser escolhido com sabedoria. Uma técnica amplamente aceita é a validação cruzada, ou seja, o valor de alfa é iterado em uma variedade de valores e é escolhido aquele que fornece maior pontuação de validação cruzada.
Dê uma olhada no valor dos coeficientes nos modelos acima:
Código Python:
In [11]:
pd.options.display.float_format = '{:,.2g}'.format
coef_matrix_ridge
Out[11]:
Isso imediatamente nos dá as seguintes inferências:
O RSS aumenta com o aumento do alfa, e esta complexidade do modelo reduz um alfa tão pequeno como 1e-15 e nos dá uma redução significativa na magnitude dos coeficientes. Como? Compare os coeficientes na primeira linha desta tabela com a última linha de tabela de regressão linear simples. Altos valores alfa podem levar a inadequações significativas.
Observe o aumento rápido no RSS para valores de alfa maiores que 1 embora os coeficientes sejam muito pequenos, eles NÃO são zero. Os primeiros 3 são muito intuitivos. Mas # 4 também é uma observação crucial.
Ridge: inclui todos (ou nenhum) dos recursos no modelo. Assim, a principal vantagem da regressão Ridge é o encolhimento do coeficiente e a redução da complexidade do modelo.
LASSO: Além do encolhimento dos coeficientes, o LASSO também faz a seleção de características. Como observamos anteriormente, alguns dos coeficientes tornam-se exatamente zero, o que é equivalente à característica particular que está sendo excluída do modelo.
Tradicionalmente, técnicas como regressão stepwise foram usadas para executar a seleção de características e fazer modelos parcimoniosos. Mas com os avanços na Aprendizagem de Máquina, regressão LASSO e Ridge fornecem alternativas muito boas à medida que dão uma saída muito melhor, exigem menos parâmetros de ajuste e podem ser automatizados em grande extensão.
Ridge: é usado principalmente para evitar o overfitting. Uma vez que inclui todos os recursos, não é muito útil no caso de #features exorbitantes, digamos em milhões, pois representará desafios computacionais.
LASSO: uma vez que fornece soluções esparsas, geralmente é o modelo de escolha (ou alguma variante deste conceito) para modelar casos em que as #features são em milhões ou mais. Nesse caso, obter uma solução esparsa é de grande vantagem computacional, pois os recursos com coeficientes zero podem simplesmente ser ignorados.
Junto com Ridge e Lasso, Elastic Net é outra técnica útil que combina a regularização L1 e L2. Ele pode ser usado para equilibrar os prós e os contras da regressão LASSO e Ridge. Encorajo você a pesquisar mais sobre isso!